package campus.aiit.ssl.manage.modbus;

import campus.aiit.ssl.manage.modbus.provider.misc.ByteUtils;
import campus.aiit.ssl.manage.modbus.provider.misc.CRC16;
import lombok.Getter;
import lombok.Setter;

/**
 * ModBus protocol Specification
 * ADU: (Additional Address, PDU(Function Code, Data), Error Check)
 *
 * @author ihaokun
 * @date 2019/12/13 17:22
 */
public class ModBus {
  /** Application Data Unit*/
  public static class ADU {
    @Getter@Setter private String address; // 1 bytes
    /** Protocol Data Unit */
    @Setter@Getter
    public static class PDU {
      /** valid codes are in the range of [1, 255] decimal */
      public enum FunctionCodes {
        READ_HOLDING_REGISTERS(0x03),//读取保持寄存器
        WRITE_SINGLE_REGISTER(0x06),//写单个寄存器
        WRITE_MULTIPLE_REGISTERS(0x10);//写多个寄存器
        // [128, 255] is reserved for Exception Function Codes
        public static boolean isException(int code){
          return code >= 128 && code <= 255;
        }

        @Override
        public String toString() {
          return String.valueOf(hex);
        }

        int hex;

        FunctionCodes(int hex) {
          this.hex = hex;
        }

        public static class ModBusFunctionCodeException extends RuntimeException {
          public ModBusFunctionCodeException() {
            super();
          }

          public ModBusFunctionCodeException(String message) {
            super(message);
          }
        }
      }

      private String funCode;
      private String data;

      public PDU() {
      }
      public PDU(String hexString) {
        this.setFunCode(hexString.substring(0, 2));
        this.setData(hexString.substring(2));
      }

      @Override
      public String toString() {
        return funCode + data;
      }
    }
    @Getter@Setter private PDU pdu;
    /** Error Check(CRC(Cyclic Redundancy Check)) 2 bytes */
    private String errorCheck(){
      // 根据address+funCode+data，计算CRC16
      String s = (address + pdu);
      String dataHEX = s.replace(" ","");
      byte[] data = ByteUtils.hexStringToBytes(dataHEX);
      int crcInt = CRC16.calcCrc16(data);
      String crcStr = String.format("0x%04x", crcInt);
      String hi = crcStr.substring(2,4);
      String lo = crcStr.substring(4,6);
      // 逆序
      return lo + hi;
    }

    public ADU() {}
    public ADU(String address, PDU pdu) {
      this.address = address;
      this.pdu = pdu;
    }
    @Override
    public String toString() {
      return (address + pdu + errorCheck()).replace(" ","");
    }
  }

}